package com.qqt.csr.common.operalog;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.filter.SimplePropertyPreFilter;
import com.google.common.collect.Lists;
import com.qqt.csr.common.session.SessionContextHolder;
import com.qqt.csr.common.utils.IPUtil;
import com.qqt.csr.common.utils.JsonUtil;
import com.qqt.csr.common.utils.ServletUtils;
import com.qqt.csr.common.utils.ThreadPoolUtil;
import jakarta.annotation.PreDestroy;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.NamedThreadLocal;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;
import org.verapdf.processor.TaskType;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.concurrent.*;

/**
 * 操作日志记录处理
 *
 * @author chenpeijian
 */
@Aspect
@Component
public class OperationLogAspect {
    private static final Logger log = LoggerFactory.getLogger(OperationLogAspect.class);

    /**
     * 排除敏感属性字段
     */
    public static final List<String> EXCLUDE_PROPERTIES = Lists.newArrayList("password", "oldPassword", "newPassword", "confirmPassword");

    /**
     * 计算操作消耗时间
     */
    private static final ThreadLocal<Long> TIME_THREADLOCAL = new NamedThreadLocal<Long>("Cost Time");

    private final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10,
            60, TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(1000),
            new ThreadPoolExecutor.CallerRunsPolicy());

    @Autowired
    private MongoTemplate mongoTemplate;

    /**
     * 处理请求前执行
     */
    @Before(value = "@annotation(operationLog)")
    public void boBefore(JoinPoint joinPoint, OperationLog operationLog) {
        TIME_THREADLOCAL.set(System.currentTimeMillis());
    }

    /**
     * 处理完请求后执行
     *
     * @param joinPoint 切点
     */
    @AfterReturning(pointcut = "@annotation(operationLog)", returning = "respResult")
    public void doAfterReturning(JoinPoint joinPoint, OperationLog operationLog, Object respResult) {
        handleLog(joinPoint, operationLog, null, respResult);
    }

    /**
     * 拦截异常操作
     *
     * @param joinPoint 切点
     * @param e         异常
     */
    @AfterThrowing(value = "@annotation(operationLog)", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, OperationLog operationLog, Exception e) {
        handleLog(joinPoint, operationLog, e, null);
    }

    private void handleLog(final JoinPoint joinPoint, OperationLog operationLog, Exception e, Object respResult) {
        try {
            SysOprationLog operLog = new SysOprationLog();
            operLog.setTenantId(SessionContextHolder.getHeaderInfo().getTenantId());
            operLog.setOperTime(new Date());
            operLog.setOperIp(IPUtil.getRequestIp());
            operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255));
            operLog.setOperUserId(SessionContextHolder.getHeaderInfo().getUserName());
            operLog.setOperUserName(SessionContextHolder.getHeaderInfo().getNickName());

            if (e == null) {
                operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
            } else {
                operLog.setStatus(BusinessStatus.FAIL.ordinal());
                operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
            }
            // 设置方法名称
            operLog.setMethod(joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()");
            // 设置请求方式
            operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
            // 处理设置注解上的参数
            operLog.setLevel(operationLog.level().getDescription());
            // 设置业务类型
            operLog.setBusinessType(operationLog.businessType().getDescription());
            // 设置功能按钮名称
            operLog.setButtonName(operationLog.buttonName());
            // 设置标题
            operLog.setOperationMenu(operationLog.operationMenu());
            // 设置操作人类别
            operLog.setOperatorType(operationLog.operatorType().ordinal());
            // 是否需要保存request，参数和值
            if (operationLog.isSaveRequestData()) {
                // 获取参数的信息，传入到数据库中。
                operLog.setChangevalue(getRequestValue(joinPoint));
            }
            // 是否需要保存response，参数和值
            if (operationLog.isSaveResponseData() && respResult != null) {
                operLog.setJsonResult(StringUtils.substring(JsonUtil.toJson(respResult), 0, 2000));
            }
            // 设置消耗时间
            operLog.setCostTime(System.currentTimeMillis() - TIME_THREADLOCAL.get());
            // 保存数据库
            saveOperationLog(operLog);
        } catch (Exception exp) {
            log.error("操作日志记录异常！", exp);
        } finally {
            TIME_THREADLOCAL.remove();
        }
    }

    /**
     * 获取请求的参数，放到log中
     *
     * @throws Exception 异常
     */
    private String getRequestValue(JoinPoint joinPoint) {
        Method targetMethod = ((MethodSignature) joinPoint.getSignature()).getMethod();
        Parameter[] parameters = targetMethod.getParameters();
        Object[] paramsArgsValue = joinPoint.getArgs();
        StringBuilder sb = new StringBuilder();
        // 技能组名称=测试1,id=444L,渠道={渠道id=123L,渠道名称=渠道1},成员=[{成员id=111L,成员名称=花花}]
        for (int i = 0; i < paramsArgsValue.length; i++) {
            Parameter parameter = parameters[i];
            OperaLogSchema operaLogSchema = AnnotationUtils.findAnnotation(parameter, OperaLogSchema.class);
            if(operaLogSchema != null){
                sb.append(operaLogSchema.desc()).append("=").append(getChangeValueDesc(paramsArgsValue[i]));
            }else if(ClassUtil.isBasicType(parameter.getType()) || String.class.isAssignableFrom(parameter.getType())){
                continue;
            }else {
                sb.append(getChangeValueDesc(paramsArgsValue[i]));
            }
            sb.append(",");
        }
        return sb.toString();
    }

    private String getChangeValueDesc(Object value) {
        if (value == null) {
            return StringUtils.EMPTY;
        }
        if (ObjectUtil.isBasicType(value) || value instanceof String || value instanceof Enum<?>) {
            return value.toString();
        }
        if (isFilterObject(value)) {
            return StringUtils.EMPTY;
        }
        if(value instanceof Date){
            return DateUtil.formatDateTime((Date)value);
        }

        StringBuilder sb = new StringBuilder();
        ReflectionUtils.doWithFields(value.getClass(), (field) -> {
            field.setAccessible(true);
            OperaLogSchema operaLogSchema = AnnotationUtils.findAnnotation(field, OperaLogSchema.class);
            if (operaLogSchema == null) {
                return;
            }
            sb.append(operaLogSchema.desc()).append("=");
            Object fieldValue = ReflectionUtils.getField(field, value);
            if (ObjectUtil.isBasicType(field.getType()) || String.class.isAssignableFrom(field.getType()) || Enum.class.isAssignableFrom(field.getType())) {
                sb.append(fieldValue);
            } else if (Collection.class.isAssignableFrom(field.getType())) {
                sb.append("[");
                if (fieldValue != null) {
                    List<String> desList = new ArrayList<>();
                    ((Collection<?>) fieldValue).forEach(item -> {
                        if (ObjectUtil.isBasicType(item.getClass()) || String.class.isAssignableFrom(item.getClass()) || Enum.class.isAssignableFrom(item.getClass())) {
                            desList.add(getChangeValueDesc(item));
                        } else {
                            desList.add("{" + getChangeValueDesc(item) + "}");
                        }
                    });
                    sb.append(StringUtils.join(desList, ","));
                }
                sb.append("]");
            } else if (Map.class.isAssignableFrom(field.getType())) {
                sb.append(JsonUtil.toJson(fieldValue, EXCLUDE_PROPERTIES));
            } else {
                sb.append("{");
                sb.append(getChangeValueDesc(fieldValue));
                sb.append("}");
            }

            sb.append(",");
        });
        return sb.toString();
    }

    /**
     * 判断是否需要过滤的对象。
     *
     * @param o 对象信息。
     * @return 如果是需要过滤的对象，则返回true；否则返回false。
     */
    @SuppressWarnings("rawtypes")
    private boolean isFilterObject(final Object o) {
        if (o == null) {
            return true;
        }
        Class<?> clazz = o.getClass();
        if (clazz.isArray()) {
            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
        } else if (Collection.class.isAssignableFrom(clazz)) {
            Collection collection = (Collection) o;
            for (Object value : collection) {
                return value instanceof MultipartFile;
            }
        } else if (Map.class.isAssignableFrom(clazz)) {
            Map map = (Map) o;
            for (Object value : map.entrySet()) {
                Map.Entry entry = (Map.Entry) value;
                return entry.getValue() instanceof MultipartFile;
            }
        }
        return o instanceof MultipartFile || o instanceof ServletRequest
                || o instanceof ServletResponse || o instanceof BindingResult;
    }

    private void saveOperationLog(SysOprationLog sysOprationLog){
        threadPoolExecutor.execute(()->{
            // sysOprationLog.setOperLocation(PconlineServiceClient.getRealAddressBy(sysOprationLog.getOperIp()));
            mongoTemplate.save(sysOprationLog);
        });
    }

    @PreDestroy
    public void destroy(){
        ThreadPoolUtil.shutdownThreadPoolGracefully(threadPoolExecutor);
    }
}
